using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
#if WINDOWS
using System.Reactive.Linq;
#elif WINDOWS_PHONE
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Phone.Reactive;
#elif SILVERLIGHT
using System.Reactive.Linq;
using Keys = System.Windows.Input.Key;
#endif
using SoTools.Communicator;
using SoSlam.Common.Communication.GameMessages;
using System.Diagnostics;
using System.Collections.Generic;
using SoSlam.Common.Communication;
using System.Threading;
using Microsoft.Xna.Framework.Input;

namespace SoSlam.Common.ClientLogic
{
    public class Player : DrawableGameComponent
    {
        internal static Guid PlayerId = Guid.NewGuid();

        private readonly SpriteBatch _spriteBatch;
        private readonly World _world;
        private readonly TrackingCamera _camera;
        private readonly Communicator _communicator;

#if WINDOWS
        private readonly KinectManager _kinectManager;
        private TimeSpan _timingPhoto;
        private TimeSpan _slamTime;

        private bool _isPictureTaken;

        private string _photoFileName;
        public string PhotoFileName
        {
            get { return _photoFileName; }
        }
#endif

        private PlayerState _playerState;

        private Vector2 _position;
        public Vector2 Position { get { return _position; } }

        private Vector2 _speed;
        private float _power;

        private Texture2D _powerBarSprite;
        private Texture2D _powerCursorSprite;

        /// <summary>
        /// Delay to wait before accepting to tap to reset
        /// </summary>
        private double tapResetDelay;

        private bool _isReady = false;
        public bool IsReady { get { return _isReady; } }

#if WINDOWS
        private Texture2D _kinectSprite;
#endif

        private AnimatedSprite _currentSprite;
        private Dictionary<string, AnimatedSprite> _animatedSprites;

        private bool _slamWasDown = false;
        private bool _flightWasDown = false;
        private bool _isAnyTouchDown = false;
        private bool _isAnyKeyDown = false;

        SpriteFont _arialSpriteFont;

        public Player(Game game, World world, TrackingCamera camera)
            : base(game)
        {
            _world = world;
            _camera = camera;
            _communicator = (Communicator)Game.Services.GetService(typeof(Communicator));

            // Player position updates
            _communicator.GenerateObservable<PlayerPositionMessage>()
                .Subscribe(HandlePlayerPositionMessage);

            // Player state updates
            _communicator.GenerateObservable<PlayerStateMessage>().Subscribe(ChangePlayerState);

            _spriteBatch = new SpriteBatch(Game.GraphicsDevice);

#if WINDOWS
            _kinectManager = new KinectManager();
#endif
        }


        #region Initialization

        public override void Initialize()
        {
            // Initialization of the animated sprites
            _animatedSprites = new Dictionary<string, AnimatedSprite>();

            _animatedSprites.Add("Idle", new AnimatedSprite(Game, new AnimationDefinition
            {
                AssetName = "Sprites/squirrel2",
                FrameRate = 10,
                FrameSize = new Point(30, 30),
                Loop = false,
                NbFrames = new Point(1, 1),
                Offset = new Point(119, 80),
                Scale = Constants.PLAYER_SCALE
            }));

            _animatedSprites.Add("Flying", new AnimatedSprite(Game, new AnimationDefinition
            {
                AssetName = "Sprites/squirrel2",
                FrameRate = 10,
                FrameSize = new Point(34, 34),
                Loop = true,
                NbFrames = new Point(2, 1),
                Offset = new Point(4, 114),
                Scale = Constants.PLAYER_SCALE
            }));

            // Crashed
            _animatedSprites.Add("Crashed", new AnimatedSprite(Game, new AnimationDefinition
            {
                AssetName = "Sprites/squirrel2",
                FrameRate = 10,
                FrameSize = new Point(34, 34),
                Loop = false,
                NbFrames = new Point(5, 1),
                Offset = new Point(87, 118),
                Scale = Constants.PLAYER_SCALE
            }));

            // Frog kicking
            _animatedSprites.Add("PandaKick", new AnimatedSprite(Game, new AnimationDefinition
            {
                AssetName = "Sprites/panda",
                FrameRate = 30,
                FrameSize = new Point(73, 95),
                Loop = false,
                NbFrames = new Point(2, 1),
                Scale = Constants.PANDA_SCALE
            }, false));

            foreach (var animatedSprite in _animatedSprites.Values)
                animatedSprite.Initialize();

            base.Initialize();

#if WINDOWS
            _kinectManager.Initialize();
            _timingPhoto = new TimeSpan(0, 0, World.Random.Next(6));
#endif
        }

        protected override void LoadContent()
        {
            _arialSpriteFont = Game.Content.Load<SpriteFont>("SpriteFonts/Arial");

            _powerBarSprite = Game.Content.Load<Texture2D>("Sprites/power_bar");
            _powerCursorSprite = Game.Content.Load<Texture2D>("Sprites/power_cursor");

#if WINDOWS
            _kinectSprite = Game.Content.Load<Texture2D>("Sprites/kinect");
#endif

            foreach (var animatedSprite in _animatedSprites.Values)
                animatedSprite.LoadContent(_spriteBatch);

            base.LoadContent();
        }

        private void ChangePlayerState(PlayerStateMessage msg)
        {
            _playerState = msg.State;
            if (_playerState == PlayerState.Flying)
                _slamWasDown = true;
            else if (_playerState == PlayerState.Waiting)
                Reset();
        }

        public void Reset()
        {
            _slamWasDown = false;
            _flightWasDown = false;
            _isReady = false;

            tapResetDelay = 0;

#if WINDOWS
            _isPictureTaken = false;
            _photoFileName = null;
#endif

            _powerupHit = null;

            _speed = new Vector2();
            _position = new Vector2(0, -290);
            _playerState = PlayerState.Waiting;

            _communicator.Publish(new PlayerCommandMessage { Command = CommandType.Reset });

            foreach (var animatedSprite in _animatedSprites.Values)
                animatedSprite.Reset();
        }

        #endregion

        #region Handle input

        private void GetReady()
        {
            _communicator.Publish(new PlayerCommandMessage { Command = CommandType.Ready, PlayerId = PlayerId });
            _isReady = true;
        }

        private void GetSlammed()
        {
            _communicator.Publish(new PlayerSlamMessage());

            _slamWasDown = true;
#if WINDOWS
            _slamTime = new TimeSpan(0, 0, 0);
#endif
        }

        private void GetFlying()
        {
            _communicator.Publish(new PlayerFlightMessage { Amplitude = 1f });
        }

        private void GetScreenTouched(Vector2 inputPosition)
        {
            var position = inputPosition;
            position = position + _camera.Position;
            position.Y = -position.Y;
            _communicator.Publish(new PlayerTouchMessage { X = position.X, Y = position.Y });
        }

        private void HandleInput(GameTime gameTime)
        {
            var resetButtonBounds = new Rectangle(GraphicsDevice.Viewport.Width / 2 - 50, GraphicsDevice.Viewport.Height / 2 - 50, 150, 150);

#if WINDOWS
            if (GamePad.GetCapabilities(PlayerIndex.One).IsConnected)
            {
                var padState = GamePad.GetState(PlayerIndex.One);

                switch (_playerState)
                {
                    case PlayerState.Waiting:
                        if (padState.IsButtonDown(Buttons.Start))
                        {
                            GetReady();
                        }
                        break;
                    case PlayerState.FallingAtStart:
                        if (padState.IsButtonDown(Buttons.A) && !_slamWasDown)
                        {
                            GetSlammed();
                        }
                        break;
                    case PlayerState.Flying:
                        if (padState.IsButtonDown(Buttons.B) && !_flightWasDown)
                        {
                            GetFlying();
                        }

                        _flightWasDown = padState.IsButtonDown(Buttons.B);

                        break;
                    case PlayerState.OnTheGround:
                        if (padState.IsButtonDown(Buttons.Back))
                            Reset();
                        break;
                }
            }
#endif

#if SILVERLIGHT || WINDOWS

#if SILVERLIGHT
            var keyboardState = Keyboard.GetState();
#elif WINDOWS
            var keyboardState = Keyboard.GetState(PlayerIndex.One);
#endif

            var mouseState = Mouse.GetState();

            switch (_playerState)
            {
                case PlayerState.Waiting:
                    if (keyboardState.IsKeyDown(Keys.Enter) && !_isAnyKeyDown)
                    {
                        GetReady();
                    }
                    _isAnyKeyDown = keyboardState.IsKeyDown(Keys.Enter);

#if WINDOWS
                    if (_kinectManager.IsReady && _kinectManager.IsHandsUp)
                    {
                        GetReady();
                    }
#endif
                    break;
                case PlayerState.FallingAtStart:
                    if (keyboardState.IsKeyDown(Keys.Space) && !_slamWasDown)
                    {
                        GetSlammed();
                    }
                    break;
                case PlayerState.Flying:
                    if (keyboardState.IsKeyDown(Keys.Up) && !_flightWasDown)
                    {
                        GetFlying();
                    }

                    _flightWasDown = keyboardState.IsKeyDown(Keys.Up);

#if WINDOWS
                    if (_kinectManager.IsReady)
                    {
                        var amplitude = _kinectManager.PollAmplitude();
                        if (amplitude != null)
                        {
                            _communicator.Publish(new PlayerFlightMessage { Amplitude = amplitude.Value / 45f });
                        }
                    }
#endif

                    if (mouseState.LeftButton == ButtonState.Pressed)
                    {
                        GetScreenTouched(new Vector2(mouseState.X, mouseState.Y));
                    }
                    break;
                case PlayerState.OnTheGround:
                    if (keyboardState.IsKeyDown(Keys.Back) && !_isAnyKeyDown)
                    {
                        Reset();
                    }

                    _isAnyKeyDown = keyboardState.IsKeyDown(Keys.Back);
                    break;
            }
#endif

#if WINDOWS_PHONE
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
				Game.Exit();

			var touchState = TouchPanel.GetState();
            if (touchState.AnyTouch() && !_isAnyTouchDown)
			{
                _isAnyTouchDown = true;

				switch (_playerState)
				{
					case PlayerState.Waiting:
						GetReady();
						break;
					case PlayerState.FallingAtStart:
                        if (!_slamWasDown)
                        {
                            GetSlammed();
                        }
						break;
                    case PlayerState.Flying:
                        if (touchState[0].State == TouchLocationState.Pressed)
                        {
                            GetScreenTouched(touchState[0].Position);
                        }
                        break;
                    case PlayerState.OnTheGround:
                        if (resetButtonBounds.Contains((int)Math.Round(touchState[0].Position.X), (int)Math.Round(touchState[0].Position.Y))
                            && tapResetDelay >= 1000)
                        {
                            Reset();
                        }
                        break;
				}
			}

            if (_playerState == PlayerState.OnTheGround)
            {
                tapResetDelay += gameTime.ElapsedGameTime.TotalMilliseconds;
            }

            _isAnyTouchDown = touchState.AnyTouch();
#endif
        }

        #endregion

        #region Powerup states

        private TimeSpan _powerupHitTime = TimeSpan.Zero;
        private TimeSpan _powerupHitTimeMax = TimeSpan.FromSeconds(1);
        private PowerUp _powerupHit;

        public void PowerupCollision(PowerUp powerup)
        {
            if (powerup != _powerupHit)
            {
                _powerupHit = powerup;
                _powerupHitTime = TimeSpan.Zero;
            }
        }

        #endregion

        #region Update & position

        public override void Update(GameTime gameTime)
        {
            HandleInput(gameTime);

#if WINDOWS
            _photoFileName = _kinectManager.FileName;
#endif

            bool updatePosition = false;

            switch (_playerState)
            {
                case PlayerState.Waiting:
                    _currentSprite = _animatedSprites["Idle"];
                    break;
                case PlayerState.FallingAtStart:
                    _currentSprite = _animatedSprites["Idle"];
                    updatePosition = true;
                    break;
                case PlayerState.Flying:
                    _currentSprite = _animatedSprites["Flying"];
                    updatePosition = true;
                    break;
                case PlayerState.OnTheGround:
                    _currentSprite = _animatedSprites["Crashed"];
                    break;
                default:
                    _currentSprite = _animatedSprites["Idle"];
                    break;
            }

            if (updatePosition && smoothing)
            {
                _speed = new Vector2(0, -10) * (float)gameTime.ElapsedGameTime.TotalSeconds * Constants.TimeAdjustmentFactor + _speed;
                _position = _speed * (float)gameTime.ElapsedGameTime.TotalSeconds * Constants.TimeAdjustmentFactor + _position;
            }

            _camera.AimAt(_position);

            _currentSprite.Update(gameTime);
            _currentSprite.Position = _position - _camera.Position;

            _animatedSprites["PandaKick"].IsActive = _slamWasDown;
            _animatedSprites["PandaKick"].Position = new Vector2(-80, 0) - _camera.Position;
            _animatedSprites["PandaKick"].Update(gameTime);

#if WINDOWS
            if (_kinectManager.IsReady && _playerState == PlayerState.Flying)
            {
                _slamTime += gameTime.ElapsedGameTime;
                if (_slamTime >= _timingPhoto && !_isPictureTaken)
                {
                    _isPictureTaken = true;
                    _kinectManager.TakePicture();
                }
            }
#endif

            base.Update(gameTime);
        }

        bool smoothing = false;

        private void HandlePlayerPositionMessage(PlayerPositionMessage message)
        {
            // With smoothing
            if (smoothing)
            {
                var serverPosition = new Vector2(message.X, -message.Y);

                var positionDifference = serverPosition - _position;
                float distanceApart = positionDifference.Length();

                if (distanceApart > Constants.SMOOTHING_MAX_DISTANCE)
                    _position = serverPosition;
                else if (distanceApart > Constants.SMOOTHING_MIN_DISTANCE)
                    _position += positionDifference * 0.1f;

            }
            else // without smoothing
            {
                _position = new Vector2((float)Math.Round(message.X), (float)Math.Round(-message.Y));
            }

            _speed = new Vector2(message.Sx, -message.Sy);
            _power = message.Power;
        }

        #endregion

        #region Draw

        public override void Draw(GameTime gameTime)
        {
            _spriteBatch.Begin();

            if (_powerupHit != null)
            {
                _powerupHitTime += gameTime.ElapsedGameTime;

                if (_powerupHitTime > _powerupHitTimeMax)
                {
                    _powerupHit = null;
                }
                else
                {
                    _currentSprite.Color = PowerUp.PowerupColors[_powerupHit.Type];
                }
            }
            else
            {
                _currentSprite.Color = Color.White;
            }

            _animatedSprites["PandaKick"].Draw(gameTime, false);

            _currentSprite.Draw(gameTime, false);

            _spriteBatch.Draw(_powerBarSprite, new Vector2((float)GraphicsDevice.Viewport.Width - 170, 40), Color.White);
            _spriteBatch.Draw(_powerCursorSprite, new Vector2((float)GraphicsDevice.Viewport.Width - 170 + _power - 4, 40), Color.White);

#if WINDOWS
            if (_kinectManager.IsReady)
            {
                _spriteBatch.Draw(_kinectSprite, new Vector2((float)GraphicsDevice.Viewport.Width - 140, (float)GraphicsDevice.Viewport.Height - 70), Color.White);
            }
#endif

            //if (System.Diagnostics.Debugger.IsAttached)
            //{
            //    _spriteBatch.DrawString(_arialSpriteFont, string.Format("Position : {0} {1}", Math.Ceiling(_position.X), Math.Ceiling(_position.Y)), new Vector2((float)GraphicsDevice.Viewport.Width - 200, 0), Color.White);
            //    _spriteBatch.DrawString(_arialSpriteFont, string.Format("Camera : {0} {1}", Math.Ceiling(_camera.Position.X), Math.Ceiling(_camera.Position.Y)), new Vector2((float)GraphicsDevice.Viewport.Width - 200, 16), Color.White);
            //}

            _spriteBatch.End();

            base.Draw(gameTime);
        }

        #endregion
    }
}

